package simframe.simulation;

import java.util.*;

// A Swarm is a collection of sim objects of a given type
// A swarm is also a simulation obect
public class Swarm<E extends SimObject> extends SimObject implements Iterable<E> {

    public static abstract class SwarmAction<E extends SimObject> {
        public abstract void exec(E obj);
    }
    
	private List<E> swarm = new ArrayList<E>();
    private Map<PeriodicAction, Long> initialActions = new HashMap<PeriodicAction,Long>();

// broadcast action to all
    public void broadcast(final SwarmAction<E> x) {
        action(new Action() {

            public void exec() {
                for (E obj : swarm) {
                    x.exec(obj);
                }
            }
        });
    }

    // broadcast action to all
      public void broadcast(final SwarmAction<E> swarmAction, final Long t) {
        action(new Action() {

            public void exec() {
                for (E obj : swarm) {
                    swarmAction.exec(obj);
                }
            }
        }, t);
    }

         // broadcast action to all
      public void broadcast(final SwarmAction<E> swarmAction, final Long t, Time.Next periodicity) {
        action(new Action() {
            public void exec() {
                for (E obj : swarm) {
                    swarmAction.exec(obj);
                }
            }
        }, t, periodicity);
    }

      // add an action to the init method
    public void addInitSwarmAction(final SwarmAction<E> action, Long t) {
        Action x = new Action() {
            public void exec() {
                for (E obj : swarm) {
                    action.exec(obj);
                }
            }
        };

        initialActions.put(new PeriodicAction(x, null), t);
    }

	// add an action to the init method
    public void addInitSwarmAction(final SwarmAction<E> action, Long t, Time.Next periodicity) {
        Action x = new Action() {
            public void exec() {
                for (E obj : swarm) {
                    action.exec(obj);
                }
            }
        };

        initialActions.put(new PeriodicAction(x, periodicity), t);
    }
    
    public Metric createSwarmMetric(final SwarmMetric<E> simMetric) {

        // constrained metric
        final Metric.Constrain<E> constraint = simMetric.getConstraint();
        
        Metric metric = new Metric(simMetric.getName(), simMetric.getUnits(),
                new Metric.Compute() {

                    public double exec() {
                        double rval = 0;
                        double num = 0;
                        for (E obj : swarm) {
                            if (!obj.dead() && (constraint == null || constraint.allowed(obj))) {
                                rval += simMetric.compute.exec(obj);
                                num += 1;
                            }
                        }

                        if (simMetric.avg && num > 0) {
                            rval /= num;
                        }

                        return rval;
                    }
                });

        double[] vals = simMetric.getNumericalRange();

        if (vals != null && vals.length > 1) {
            metric.setRange(vals[0], vals[1]);
        }

        return metric;
    }
    
	// create swarm
	public void add(E sim) {
        sim.setOwner(this);
		swarm.add(sim);
	}
	
	public void addAll(Iterable<? extends E> src) {
		for(E sim : src) {
			swarm.add(sim);
		}
	}
	
	public boolean dead() {
		
		for(E sim : swarm) {
		
			if (!sim.dead()) {
				return false;
			}
		}
		
		return true;
	}

    @Override
	public void reset() {
        
		for(E sim : swarm) {
			sim.reset();
		}

        // now post swarm actions
        for (PeriodicAction x : this.initialActions.keySet()) {
            action(x.x, initialActions.get(x), x.period);
        }
	}

    // select a random object
	public E selectRandom() {
		 
        int index = (int) (Math.random() * swarm.size());
        
        return swarm.get(index);
	}

    public E getAtIndex(int index) {
        return swarm.get(index);
    }

	public Iterator<E> iterator() {
		return  swarm.iterator();
	}
	
	public int size() {
		return swarm.size();
	}
}
